\chapter{Fossil定制 - TH脚本语言}

\section{TH简介}

\subsection{TH是一个类Tcl语言}

TH是一个基于TCL语言和字符的命令式语言。这个语言只有很少的基本构造和相当简单的语法，也就是非常简单的意思。TH被设计为\textquotedblleft{}Fossil\textquotedblright{}源码管理系统的脚本语言。TH是一个解释型语言，它在执行脚本时进行解析、编译和执行。在Fossil里，TH脚本的典型应用时创建内建网站的页面，经常用于对表单提交进行响应。

TH的基础机制都与字符和字符替换有关联。TH/Tcl的做事情的方式于其他一些你已经很熟悉的语言相比有一些不同，所以保证你理解基本概念是非常有价值的。


\section{ \textquotedblleft{}Hello, world\textquotedblright{}程序}

按传统，开始介绍一个语言的一个标准用例是\textquotedbl{}Hello, World\textquotedbl{}程序。在TH里这个程序只有一行：
\begin{lyxcode}
puts~\textquotedblleft{}Hello,~world\textbackslash{}n\textquotedblright{}
\end{lyxcode}
在TH里输出一个字符的命令是“puts”，Puts命令后面的单独的文本内容会被打印到输出流中。如果字符多余一个单词，你必须使用双引号或者波浪号将其包裹在内。引号或者波浪号内的单词会被视为一个单独的单元，当文字被空格拆分后，会被当做命令的其它参数使用。


\section{TH结构和语法}


\subsection{数据类型}

TH内部核心中只有一个数据类型，也就是字符串。TH里的所有值都是字符串，变量保留字符串并生成返回字符串。 TH里的字符由单字节的字符组成以零结束。超过ASCII区间的字符，例如在区间0x80-0xff内的字符对于TH来说没有意义：它们不考虑数字，字符甚至是空格。

基于上文所述，TH可以有四种方式解释一个字符串。第一是就是由一连串的任意字符组成；第二是将字符认为是一个列表，一个有顺序的由空格分隔的单词组成；第三是一个字符可以是一个命令,一个命令的意思是第一个单词被认为是一个命令，后面跟随的是一连串的参数；第四，最后一种，字符可以被解释为一个表达式。

下面我们详细讨论字符串前三个解释方式。


\subsection{列表}

TH里的一个列表由一串排列顺序的对象或者被空格分隔的单词组成。TH中下面的字符由空格组成。
\begin{lyxcode}

logo
FossilBook
Artifact Content
Logged in as frans
Home Timeline Files Branches Tags Tickets Wiki Admin Logout
Download Hex Shun
Artifact 7b03c1c83fcd092b090ada102ed76356e9496f2d

    File fossilbook.lyx
        2010-06-26 12:28:12 - part of checkin [9ad19c40d6] on branch trunk - Added section on TH scripting [b832f46d31] (user: jim) [annotate] 

 
logo
FossilBook
Artifact Content
Logged in as frans
Home Timeline Files Branches Tags Tickets Wiki Admin Logout
Download Hex Shun
Artifact 7b03c1c83fcd092b090ada102ed76356e9496f2d

    File fossilbook.lyx
        2010-06-26 12:28:12 - part of checkin [9ad19c40d6] on branch trunk - Added section on TH scripting [b832f46d31] (user: jim) [annotate] 

 ~~~~~'~'~~~~0x20

~~~~~'\textbackslash{}t'~~~0x09

~~~~~'\textbackslash{}n'~~~0x0A

~~~~~'\textbackslash{}v'~~~0x0B

~~~~~'\textbackslash{}f'~~~0x0C

~~~~~'\textbackslash{}r'~~~0x0D
\end{lyxcode}
一个单词会是任何由空格限定的字符序列。单词不管是字母还是数字的：\textquotedblleft{}.\%{*}\textquotedblright{}在TH里是一个有效的单词。简单地说一个单词包含嵌入的空格字符，它需要被引号标识，不管是双引号或者开始结束的波浪号。引号有将引号内的内容放入单独的列表元素里的效果。

单词不能以TH的特殊字符\{ \} {[} {]} \textbackslash{} ; 和 \textquotedbl{}开始。注意单引号\textquoteleft{}在TH里不是一个特殊字符。必须避免使用这些字符作为字符串开始，后面将深入进行讨论。

TH提供了很多内建的命令用于列表操作，如计算单词的字符数量，使用列表索引检索或添加单独的单词。这些命令会在章节\textquotedblleft{}使用列表\textquotedblright{}中进行讨论。


\subsection{命令}

TH将所有的东西都按命令的模式来解释，甚至是程序结构如变量分配和过程定义。TH只增加了非常少的语法来正确地调用命令，然后就可以将工作重点移动到命令的实现上来。

命令是一个特殊格式的列表。基本的TH命令语法如下：
\begin{lyxcode}
command~arg1~arg2~arg3~...
\end{lyxcode}
这个命令不管是内建的命令或者是一个TH过程。

空格用来将命令名称和它的参数区分开来，一个新行或者分号可以用来结束一个命令。TH的注释是以符号\textquotedblleft{}\#\textquotedblright{}开始的行，或者是在一行命令结束的分号后面添加符号\textquotedblleft{}\#\textquotedblright{}然后进行注释。


\subsection{分割 \& 转义}

TH不会解释命令的参数来执行分割，这里允许在一个参数里有多个单词，转义用于处理特殊字符以提取变量来执行内嵌的命令。因此，TH命令处理程序的表现可以概述为下述三个步骤。
\begin{enumerate}
\item 参数分割
\item 值替代为反斜杠、变量和内嵌命令
\item 命令调用
\end{enumerate}
注意没有评估命令参数的步骤。完成替换后，参数会逐字地传递给命令而由命令来验证它的参数是否需要。


\subsubsection{参数分割}

TH有二个机制将多个单词分割成单独的对象：
\begin{itemize}
\item 双引号, \textquotedblleft{} \textquotedblright{}
\item 花括号, \{ \}
\end{itemize}
它们都有将一组单词分组的效果，它们在下一阶段的转义时有不同的影响。大概是，双引号将内容分组而花括号会分组且会将阻止内容里的转义。

分组从字符的左边开始到右边且不会影响后面转义。如果一个转义指向了一个可以不同分割的字符时，它不会起作用，因为其在分割阶段已经决定了。


\subsubsection{值转义}

TH执行三个不同的替换(查看th.c/thSubstWord代码获取详细信息)
\begin{itemize}
\item 转义反斜杠
\item 转义变量
\item 转义内嵌的命令
\end{itemize}
和分割一样，转义也是从左边向右边且只执行一次：如果一个转义指向了一个还可以被转义的字符串时，不会有任何效果。


\subsubsection{反斜杠转义}

总体来说，反斜杠(\textbackslash{})会禁止替换反斜杠后面的字符串。任何反斜杠后面的字符任然会保持原样。 当想要忽略特殊意思的字符\{ \} {[} {]} \textbackslash{}; 和\textquotedbl{}时反斜杠非常有用。

有二个在转义阶段会被转义成特殊字符的字符。一个反斜杠跟着一个字母\textquoteleft{}n\textquoteright{}会被转义为新行符号，和C语言一样。一个反斜杠跟随一个字母\textquoteleft{}x\textquoteright{}及二个十六进制数字会被转义为其对于的字符。例如：写\textquotedblleft{}\textbackslash{}x20\textquotedblright{}和输入一个空格是一个意思。需要注意字母\textbackslash{}x转义不会\textquotedbl{}一直\textquotedbl{}转义反斜杠后面的值，除非他还是Tcl里的十六进制值，需要强调的是只有二个值。字符串\textbackslash{}x2121不是一个单独的感叹号，而是三个字符!21。


\subsubsection{变量转义}

和其它编程语言一样，TH也有变量的概念。TH变量是一个保存字符串值的命名容器。变量会在下面的文章里详细探讨，我们将目前讨论的范围限制在变量转义。

美元符号(\$)可能用来作为转义变量值的特殊写法。如果\$出现在一个参数里且没有用花括号关闭那么会执行变量转义。美元符号\$后面的字符串如上文所述不是一个数字、字符或者下划线。会被认为是一个变量名称，那个变量所对应的字符串值会被根据其名称进行转义。比如：如果变量foo的值为test，那么命令\textquotedblleft{}puts \$foo.c\textquotedblright{}等同于下文的命令：
\begin{lyxcode}
\textquotedblright{}puts~test.c\textquotedblright{}
\end{lyxcode}
变量转义有二种特殊的格式。如果变量的名称的下一个字符是一个开启的圆括号，那么这个变量会被认为是一个数组，圆括号内的字符都会认为是一个数组的索引。命令转义和变量转义会在将圆括号内的信息作为索引来执行。例如：如果变量x是一个数组且有一个名为index且值为87和另外一个名为14值为more的两个元素，那么命令：
\begin{lyxcode}
puts~xyz\$x(first)zyx
\end{lyxcode}
等同于命令：
\begin{lyxcode}
puts~xyz87zyx
\end{lyxcode}
如果变量index有一个值“14”，那么命令：
\begin{lyxcode}
puts~xyz\$x(\$index)zyx
\end{lyxcode}
等同于命令：
\begin{lyxcode}
puts~xyzmorezyx
\end{lyxcode}
查看上文变量章节和下文的数组章节以了解数组的更多信息。

第二个变量的实现格式是当美元符号\$后面跟着花括号，这时变量名称为括号开始到结束的所有字符串。这个格式中不能使用数组引用：花括号内的指向的是一个标量。例如：如果变量foo有一个值叫”test“，那么命令：
\begin{lyxcode}
set~a~abc\$\{foo\}bar
\end{lyxcode}
等同于命令：
\begin{lyxcode}
set~a~abctestbar
\end{lyxcode}
一个美元符号后面加上其他的如字母、数组、下划线或者左括号会当作文字美元符号。下面的命令输出一个单独的\$。
\begin{lyxcode}
puts~x~\$
\end{lyxcode}

\subsubsection{命令转义}

最后一个构造是命令转义。一个内嵌的命令用方括号{[} {]}来限定其内容。TH解释器会读取括号内的所有内容并将其作为命令来处理。它会复述括号外的内容并将括号及其内的内容替换为内嵌命令的运行结果。

例子：
\begin{lyxcode}
puts~{[}string~length~foobar{]}

=>~6
\end{lyxcode}
在这个例子里，内嵌的命令式是：string length foobar 。这个命令返回字符foobar的长度值。内嵌命令首先运行，然后，命令转义触发外部命令并替代为其计算结果：
\begin{lyxcode}
puts~6
\end{lyxcode}
如果在一个命令里有多个命令转义触发点，解释流程的顺序是从左至右。每当遇到一个右括号就认为该命令就此结束。结果会明确地排序，即哪个内嵌命令先执行然后将其结果作为外部命令的参数。


\subsubsection{再述参数分隔}

在命令评估转义阶段，波浪号和双引号二组运算符在TH解释器里的处理不相同。双引号仅用于当内容在空格后的分割，字符a\textquotedblright{}b\textquotedblright{}是一个含有四个字符的字符串，而不是二个字符ab。
\begin{lyxcode}
puts~a\textquotedblright{}b\textquotedblright{}

=>~a\textquotedblright{}b\textquotedblright{}
\end{lyxcode}

分割在波浪号内的单词时，会禁止括号内的转义。再次提示，一个开放的花括号仅在其在空格后才会进行分隔。花括号内的字符串会直接传递给命令，甚至是在反斜杠处理完成后。

需要注意的是花括号的这个效果只有其被作为分割时才有效（如：在单词序列的开始和结束）。如果一个字符串已经分割，不管是双引号还是花括号，或花括号在一个分割的字符串中间（如：\textquotedbl{}foo\{bar\textquotedbl{}），花括号会被作为普通字符处理而没有任何特殊意义。如果字符串使用双引号进行分割，引号内的字符会触发转义，甚至是在括号之间。

方括号语法用于命令转义而不含分割功能。作为替代，内嵌的命令会被认为是当前分割字符的一部分。在下面的命令，双引号将最后的参数分割了，但内嵌命令只是那个分组的一部分：
\begin{lyxcode}
puts~\textquotedbl{}The~length~of~\$s~is~{[}string~length~\$s{]}.\textquotedbl{}
\end{lyxcode}
如果一个参数仅有一个内嵌命令组成，那么你不需要使用双引号来分割，因为TH语法会将整个内嵌命令一个分组的一部分进行处理。一个内嵌命令会作为一个完整有序的字符处理，而不管其内部结构。在采集主命令的参数时，它包含了分组字符周边的字符。


\subsection{概要}

下面的规则概述了TH解释器在调用命令之前进行分割和转义的基本机制：
\begin{itemize}
\item 命令参数使用空格进行分隔，除非参数使用花括号或者双引号使用如下方式进行分割。
\item 使用花括号进行分割时，\{\}会阻止转义。花括号嵌套。解释器匹配分组内从左至右所有的字符，包括新行、分号、嵌套花括号。编码（如：最外部的）花括号不包含在组内。
\item 使用双引号分割时，\textquotedbl{} \textquotedbl{}，允许转义。解释器会将所有的东西进行分割，直到发现另外一个双引号，包含新行、引号。嵌入的引号不包含在字符的分组内。可以使用反斜杠使双引号字符放进一个分组（如：\textbackslash{}\textquotedbl{}）。
\item 分割操作会在转义之前执行，也就是说变量的值或者命令的执行结果不会影响分割。
\item 美元符号\$会触发变量转义。变量名可以由任意长度的字母组成。如果变量引用了其它嵌套的字符串，或者如果其包含其它如字母，下划线，可以使用\$\{varname\}语法进行区别。
\item 方括号{[}{]}会触发命令转义。在括号内的所有东西都会被作为命令进行处理，命令里的所有内容都会被命令执行结果替换掉。允许嵌套。
\item 反斜杠\textbackslash{}用来引用特殊字符。你可以认为它是另外一种格式的转义，反斜杠及其后面的字符会被一个新的字符替代。
\item 转义会在任意地方触发，除非使用花括号阻止。分组的一部分可以是常量字符串，另外的部分可以是转义后的结果。甚至是命令的名称也可以使用转义处理。
\item 一个单独的转义会在命令引用之前执行。转义的结果不会被再次解释。如果你有包含特殊字符如空格、美元符号、方括号或者花括号的变量值或者命令结果时这个规则会很重要，因为只会执行一个循环的转义，而你不用担心特殊字符的值会触发一个额外的转义。
\end{itemize}

\subsubsection{警告}
\begin{itemize}
\item 一个比较普遍的错误是在使用花括号或引号对参数分割时忘记参数间的空格。这是因为当使用花括号或引号来进行分割时，空格被当做了间隔符。如果你忘记空格，会引起一个关于参数数量错误的语法错误。下面的错误是因为在 \} 和 \{ 之间忘记了空格：

\begin{lyxcode}
if~\{\$x~>~1\}\{puts~\textquotedbl{}x~=~\$x\textquotedbl{}\}
\end{lyxcode}
\item 当使用双引号进行分割时，花括号的特殊效果会被关闭。双引号格式内的任意地方都会触发转义。在下面的命令里，变量仍然会被转义：

\begin{lyxcode}
set~x~xvalue

set~y~\textquotedbl{}foo~\{\$x\}~bar\textquotedbl{}

=>~foo~\{xvalue\}~bar
\end{lyxcode}
\item 使用方括号进行命令转义时不需要使用空格。在进行分割时，解释器会将方括号内的所有东西识别为当前分组的一部分。
\end{itemize}

\section{TH表达式}

TH解释器本身不会执行数学表达式。TH只做分割、转义和命令调用。然而，几个内建的命令见到一个或多个参数作为表达式时，会请求解释器计算此类表达式的值。

命令\textbf{expr command}是一个最简单的此类命令，用于解析并执行表达式：
\begin{lyxcode}
puts~{[}expr~7.4/2{]}

=>~3.7
\end{lyxcode}
注意表达式可以包含空格，但是如果有的话必须进行分割以将其识别为一个单独的参数。

TH在执行表达式时表达式的内容可使用三种数据类型：二种类型的数字，布尔值和浮点数，字符。正整数会转化成浮点值。布尔值True和False也可以使用正整数值1和0来替代。expr的实现非常小心地处理精确度并避免不必要的字符与数字的转化。

在表达式起效之前，变量和命令转义都会在表达式字符上执行。因此，你可以在数学表达式内包含变量引用及内嵌命令，甚至是在表达式字符包含在花括号内时。注意反斜杠转义在表达式里不会被执行。

TH表达式由操作数、运算符及圆括号组成。在它们之间可能会使用空格；空格会被表达式处理器忽略。当可能时，操作会被解读成整数值。如果一个操作数不是正整数格式，如果可能的话它会被解读成浮点数。浮点数在ANSI-标准的C编译器里会被通过多个方式规定。比如，下面的都是有效的浮点数：2.1，3.，6e4， 7.91e+16。如果不能进行数值化转换，那么操作数会被认为是字符（只有少数的运算符可以使用它）。

操作数会应用下面任意一种方式被解读：
\begin{itemize}
\item 数字值，不管整数还是浮点数。
\item 花括号内的字符串，括号内的字符串会被作为操作数使用且不会被转义。
\item 双引号内字符串，表达式剖析引号内的信息的变量和命令转义，使用它们的结果作为操作数来执行。
\item TH变量，使用\$声明，变量的值会被作为操作数来使用。
\item 方括号里的TH命令，命令会执行且它的结果会被作为操作数来使用。
\end{itemize}
如上触发转义的地方（例如：引号内的字符），它们会被处理器执行。然而，在表达式处理调用前，还有额外的一层转义可以会被命令剖析器执行。如下讨论，一般最好使用花括号来分隔表达式以阻止命令剖析在内容里执行转义。 

有效的运算符如下，根据优先级由高到低排列：

\noindent \begin{center}
\begin{tabular}{|c|>{\raggedright}p{3.8in}|}
\hline 
\textbf{运算器} & \textbf{动作}\tabularnewline
\hline 
\hline 
+ - \textasciitilde{} ! & 一元加、一元减、比特位NOT、逻辑NOT。这些运行都不会运用在字符操作数上，比特位NOT可能会被应用于整数。\tabularnewline
\hline 
{*} / \% & 乘法、除法、余数。它们都不会被引用在字符操作数上，余数只会被应用于整数。\tabularnewline
\hline 
+ - & 加和减。只用于数值操作数验证。\tabularnewline
\hline 
<\textcompwordmark{}< >\textcompwordmark{}>  & 左移和右移。仅用于整数操作数。\tabularnewline
\hline 
< > <= >= & 布尔运算小于、大于、小于等于和大于等于。每个运算符都会在条件为真时返回1为假时返回0。这些运算符会应用于字符操作数，同样也可以用于数值操作数，如字符串对比的案例。\tabularnewline
\hline 
== !=  & 布尔运算等于和不等于。同上该运算返回0/1结果。可用于验证所有的操作数类型。\tabularnewline
\hline 
eq ne & 比较二个字符串是相同(eq)还是不同(ne)。它返回1 (true)或者0 (false)。这个运算可以用于验证字符串，但是不包含数字: \tabularnewline
\hline 
\& & 比特位AND，只用于整数操作数。\tabularnewline
\hline 
\textasciicircum{} &  Bit-wise XOR. Valid for integer operands only\tabularnewline
\hline 
| & 比特位OR，只用于整数操作数。\tabularnewline
\hline 
\&\& & 逻辑计算AND，产生一个返回结果，1表示二个操作数都不是零，否则为0。仅用于整数操作数。注意：没有\textquotedblleft{}求值捷径\textquotedblright{}： 尽管左侧求得值为False还是会求右侧的值。\tabularnewline
\hline 
|| &  逻辑计算OR，产生一个返回结果，1表示二个操作数都不是零，否则返回0。仅用于整数操作数。注意：没有\textquotedblleft{}求值捷径\textquotedblright{}： 尽管左侧求得值为False还是会求右侧的值。\tabularnewline
\hline 
\end{tabular}
\par\end{center}

所有优先级相同的二进制运算都从左至右执行。比如表达式“  4{*}2 < 7  ”返回值为0。 

所有牵涉到整数的内部计算都使用的C语言的int类型，所有牵涉浮点的内部运算都是使用的C语言的double类型。所有的内部整数、浮点、字符操作数的转换都会根据需要自动执行。算法运算时，直到浮点被引入且点被使用之后才会使用整数。

字符串值可能作为比较处理器的操作数使用，尽管表达式会尝试进行整数和浮点的转换。如果相比较的操作数一个是字符另外一个是数值，那么数值操作数会被转回字符。


\section{TH变量}

和所有的编程语言一样TH也有变量的概念。TH变量绑定一个变量名到一个字符串值。变量名在其范围内必须唯一，不管是全局或者本地范围。TH支持二种类型的变量：常量和数组。

TH允许通过使用\$样式、set命令或其它少数构造的变量转义来定义和使用变量。变量不需要预定义：当一个新的变量名被使用时会自动创建一个变量。


\subsection{使用变量}

TH有二个关键命令用于操作变量，set和unset：
\begin{lyxcode}
set~varname~?value?

unset~varname

info~exists~varname
\end{lyxcode}

\textbf{ set command}命令返回变量名对应的值。如果变量不存在，那么会抛出一个错误。如果可选参数并指定，那么set命令设置\textit{varname}的值。如果不存在，那么在当前范围内创建一个新的变量并返回它的值。

\textbf{unset command}命令会从其范围内删除一个变量。参数\textit{varname}是变量的名称。如果\textit{varname}引用了一个数组的元素，那么那个元素会移除而不会影响剩余的数组元素。如果\textit{varname}有一个数组的名称组成且不含括号的索引值，那么整个数组会被删除。\textbf{unset command}会返回一个空字符作为返回值。如果变量不存在那么会引起一个错误。 

命令\textbf{info exists command}当变量名\textit{varname}存在于当前范围是返回“1”，不管是在全局还是当前范围；否则返回“0”。


\subsubsection{标量变量和数组变量}

TH支持二种类型的变量：标量和数组。一个标量变量只有一个单独的值， 反之一个数组变量可以有任意数量的元素，每一个都有对应的名称（命名为它的\textquotedbl{}索引\textquotedbl{}）及值。TH数组是一个可标识关联的数组，即索引可以是任意的字符串。

如果\textit{varname}包含一个左圆括号并以右圆括号结束，那么它索引了一个数组元素：括号前的字符是数组的名称，括号内的字符是它的索引。否则\textit{varname}就定义为一个标量变量。

比如，命令 \textbf{set {[}x(first) 44{]}}会将变量X索引为first的元素的值设置为新值44。TH中的二个标识数组可以使用指数模拟多个串联值。比如如下命令：
\begin{lyxcode}
set~a(2,3)~1

set~a(3,6)~2
\end{lyxcode}
设置指数为2,3和3,6的元素。

大概地说，TH中数组元素可以在任意标量也被使用的地方使用。如果一个数组并定义为一个特殊的名称，那么就不可以使用该名称同时（同范围）定义一个标量变量。同样的，如果有一个标量变量使用了特定名称，那么就不能使用其作为数组变量使用。要转化一个标量为数组，应使用\textbf{unset}命令删除存在的变量，反之亦然。


\subsubsection{变量范围}

变量存在于一个范围内。TH解释器维护了一个全局范围，就是可以为所有的命令执行时可以共享的变量。每一个用户自定义命令的调用都会创建一个本地范围。这个本地范围保存参数及本地变量，仅存在于用户命令的执行期间。

如果不在用户命令的实现主体，那么对varname的引用会指向一个全局的变量，如，一个在全局范围的变量。相反的，在用户自定义命令的实现主体里对varname的引用指向了一个参数或者命令的本地变量。然但是，在用户自定义命令的实现主体部分可以使用在全局变量前使用”::“来明确指定。

TH提供了一个特殊命令来连接不在当前命令的本地范围而是命令调用链的本地范围来限制到当前命令的范围里。这个命令是upvar：
\begin{lyxcode}
upvar~?frame?~othervar~myvar~?othervar~myvar~...?
\end{lyxcode}
命令\textbf{upvar command}将当前程序的一个活多个本地变量排序指向一个封闭的程序调用，或者指向全局变量。如果\textit{frame}是正整数，那么就给出一个移动的距离（提升命令调用堆栈）。如果\textit{othervar}不是正整数，参数\textit{frame}可能会省略（之后frame默认是“1”）。 对于每一个\textit{othervar}参数，\textbf{upwar}命令在本地范围通过名称可辨别、\textit{myvar}对应参数的给定的名称以及在当前程序通过\textit{frame}偏移可连接性来构造变量。由\textit{othervar}命名的变量不需要在调用时就存在；它会在myvar的第一次引用时被创建，就像普通变量一样。\textit{upvar}命令只在用户定义命令的主体部分有意义。不管是Othervar还是myvar都不会指向数组的元素。\textit{upvar}命令用以简化\textit{call-by-name}程序调用的实现也使得创建一个新TH命令的控制结构变得简单些。比如，考虑如下程序：
\begin{lyxcode}
proc~incr~\{name\}~\{

~~~~upvar~\$name~x

~~~~set~x~{[}expr~\$x+1{]}

\}
\end{lyxcode}
\textbf{incr}通过一个给定名称的变量进行了调用，它增加了那个变量的值。	


\section{TH命令，脚本和程序流程}

在TH里事实上在命令（通常在其它语言里理解为\textquoteleft{}statements\textquoteright{}或“functions”）和\textquotedbl{}syntax\textquotedbl{}没有差别。没有类似C、Java、Python、Perl之类语言的保留词汇。当TH解释器开始发现有一系列内建的、已知的命令那么解释器一般会解析一行代码。这些命令包含for、set、puts等等。他们包括所有的TH命令，然而，TH命令依然只经常遵从同样语法规则，无论是内建或那些你使用proc命令创建的TH命令。


\subsection{再述命令}

和Tcl一样，TH由命令构成。一个命令为你做某些事情，例如输出一个字符，计算一个数学表达式，或者生成HTML以在屏幕显示一个部件。 

命令是列表的的一个特殊形式。TH命令的基本语法如下：
\begin{lyxcode}
command~arg1~arg2~arg3~...
\end{lyxcode}
\textbf{command}不管是内建命令还是一个TH程序。

使用空格来分隔命令及其参数，用新行符号或者分号来结束一个命令。TH语言的行注释使用符号\textquotedblleft{}\#\textquotedblright{}及后面的字符串构成，或者在命令结束分号后面添加一个\textquotedblleft{}\#\textquotedblright{}及注释文字。


\subsection{脚本}

一般情况下，TH里的控制遵循从一个命令到下一个命令的顺序。下一个命令也可以在同一个列表里（若当前命令使用分号结束时）或在下一行。因此TH程序是一个有TH命令组成的列表。

这样的命令列表指向了一个\textquotedblleft{}script\textquotedblright{}。一个脚本因此是一个自包含的代码片段由一个或多个命令构成。 脚本里的命令类型其它语言里的声明。

有些命令使用一个活多个脚本作为参数，取决于其它的参数运行这些脚本零或多次。比如，如果命令执行那个脚本或者其它脚本一次，取决于表达式的执行结果为True或False。命令执行脚本会执行一般的分割及转移，作为执行脚本的一部分。
\begin{quote}
请注意脚本都需要使用花括号来结束以阻止转义执行第二次：一次作为执行上层命令的一部分及在准备脚本时执行一次。忘记使用花括号关闭脚本参数是常见的源码错误。
\end{quote}
少数的命令(return, error, break and continue)会立即停止当前执行的脚本，并将控制权交给列表的下一个命令。控制取代了返回到了当前脚本的初始化了的命令执行。 


\subsection{命令结果代码}

每个命令都会产生二个结果：一个结果代码和一个字符串。代码显示不管是命令成功或者{\scriptsize 不成功}，字符串会给出额外的信息。有效的代码包含在“th.h”头文件里，如下：

\noindent \begin{center}
\begin{tabular}{|c|c|>{\raggedright}p{3.5in}|}
\hline 
\textbf{名称} & \textbf{值} & \textbf{含义}\tabularnewline
\hline 
\hline 
TH\_OK & 0 & \multicolumn{1}{l|}{这个是普通的返回代码。字符串会给出命令的返回值}\tabularnewline
\hline 
TH\_ERROR & 1 & 表明发生了一个错误；字符包含了错误信息的描述\tabularnewline
\hline 
TH\_RETURN & 3 & 表明调用了返回命令，字符包含程序或命令的返回值\tabularnewline
\hline 
TH\_BREAK & 2 & 表明内部的循环将立即结束，字符包含了 \textquotedblleft{}break\textquotedblright{}或者中断的参数，如果有的话\tabularnewline
\hline 
TH\_CONTINUE & 4 & 表明内部循环将进入下一次迭代。字符包含了\textquotedblleft{}break\textquotedblright{}或者中断的参数，如果有的话\tabularnewline
\hline 
\end{tabular}
\par\end{center}

TH程序员一般不会考虑返回的代码，因为总是会返回TH\_OK结果。如果其它结果被返回，那么TH解释器会立即停止命令执行并返回结果给调用的代码。如果TH解释器里有多个内嵌的调用进程，那么这些进程都会返回错误结果给调用代码，直到最终的报告被提交到程序代码的顶层。然后程序会为用户显示错误信息。

在少数情况下，一些命令会自己处理已知的 \textquotedbl{}错误\textquotedbl{}条件且不会向上层返回结果。比如，for命令会检查TH\_BEREAK代码；如果触发了，for会停止执行主体循环并返回TH\_OK给调用代码。for命令还会处理TH\_CONTINUE 代码并交由解释器处理TH\_RETURN代码。catch命令允许TH程序捕捉错误并处理他们且将会不会警告命令。


\subsection{流程控制命令}

TH里的流程控制命令如下：
\begin{lyxcode}
if~expr1~body1~?elseif~expr2~body2?~?~?else?~bodyN?

for~init~condition~incr~script

break~~~~?value?

continue~?value?

error~~~~?value?

catch~script~?varname?
\end{lyxcode}
下面按顺序讨论每一个命令

\textbf{if 命令}的语法如下：
\begin{lyxcode}
if~expr1~body1~?elseif~expr2~body2?~?~?else?~bodyN?
\end{lyxcode}
参数\textbf{expr}是表达式，body参数是脚本，elsif和else是参数是关键字常量字符。if命令选择body脚本执行来执行。

expr参数必须求解为一个正整数值。如果求值为非零值，执行后面body脚本且从根据脚本执行的返回继续if命令后面的命令。如果expr参数求值为零，它的body脚本会被跳过且尝试下一个选项。当没有更多的选项可以尝试时，任然继续执行下一个命令。

if命令返回执行脚本的值，当没有执行脚本时返回\textquotedblleft{}0\textquotedblright{}。

\textbf{for 命令}的语法如下：
\begin{lyxcode}
for~init~condition~incr~body
\end{lyxcode}
init，incr 和body参数都是脚本。条件参数是一个产生一个正整数结果的表达式。for命令是一个循环命令，和C语言里的声明结构类似。

for命令在TH解释器被调用执行init命令。然后它重复地将表达式作为条件进行求解；如果结果是非零它在TH解释器里调用body，然后再调用incr，再然后重复循环。当测试求值为零时命令终止。

如果一个continue命令在body脚本中被调用那么任何当前执行body里的剩余命令将会被跳过；流程会继续执行incr，然后求解条件，在继续。如果一个break命令在body或者next里被调用，那么for命令将会立即返回。break和continue的执行方式和C语言中类似声明的执行方式非常相似。

for命令返回一个空白字符。

\textbf{break 命令}的语法如下：
\begin{lyxcode}
break~?value?
\end{lyxcode}
break从当前程序（或顶层的命令）命令立即返回，将结果作为返回值和TH\_BREAK作为返回代码返回。如果结果没有特殊指定，字符\textquotedblleft{}break\textquotedblright{}将作为结果返回。

\textbf{continue 命令}的语法如下：
\begin{lyxcode}
continue~?value?
\end{lyxcode}
continue命令从当前程序（或顶层命令）立即返回，将值作为返回值和TH\_CONTINUE作为返回代码返回。如果值没有特别指定，那么字符\textquotedblleft{}continue\textquotedblright{}将会被作为结果返回。

\textbf{error 命令}的语法如下：
\begin{lyxcode}
error~?value?
\end{lyxcode}
error命令从当前程序（或顶层命令）立即返回，将值作为返回值、将TH\_ERROR作为结果返回，如果值没有特别指定，那么字符\textquotedblleft{}error\textquotedblright{}将会被作为结果返回。

\textbf{catch 命令}的语法如下：
\begin{lyxcode}
catch~script~?varname?
\end{lyxcode}
\textbf{catch}命令可能用来阻止命令解析中止错误。catch命令递归地调用TH解释器执行脚本，且永远都返回TH\_OK代码，不管执行脚本时发生何种错误。

\textbf{catch}的返回值是一个十进制的字符包含TH解释器执行脚本后返回的代码，如果命令没有错误它可能是”0“（TH\_OK），否则它是上文表格所对应结果代码的一个非零值。如果给定varname参数，那么它将给出变量的名称；catch从运行的脚本设置返回变量的值（或者一个结果或一个错误信息）。


\subsection{创建用户自定义命令}

\textbf{proc 命令}可以创建一个新命令。proc命令的语法如下：
\begin{lyxcode}
proc~name~args~body
\end{lyxcode}
\textbf{proc}命令创建一个新的TH命令程序，名称，使用名称取代任意存在相同名称的命令。不管何时调用命令，TH解释器将会执行命令包体里的内容。 

参数args指定了程序正式参数。它考虑列表，可能为空，谁的每一个元素指定一个参数。每个参数指定也是一个列表无论一个还是二个单元。如果只指定一个单，那么它就是参数的名称；如果有二个单元，那么第一个是参数的名称第二个是它的默认值。花括号和反斜杠可能会被用来特别指定完整的默认值。

\textbf{proc}命令返回一个null字符串。


\subsection{执行用户自定义命令}

如果一个命令是用户自定义的命令（如使用proc命令创建的命令），那么TH解释器会创建一个本地变量环境，绑定合法参数到其真实的值上（如TH唯一地使用调用值）然后加载脚本的包体。执行然后处理脚本的第一个命令。当最后一个命令结束或者返回命令被执行后脚本执行结束。当脚本结束后，本地变量环境会被删除；用户自定义命令后面的命令将会继续被执行。

更详细地说，当调用一个用户自定义命令时，本地变量环境为程序的每一个合法参数创业变量；变量的值会对应地从调用命令指定的参数赋为默认值给程序参数。有默认值的参数不需要在程序调用过程中特别指定。然而，它们必须是没有默认值的合法参数的调用参数 ，且不能有额外的调用参数。

有一个特殊的情况允许程序有参数数量成为变量。如果最后一个合法参数的名称为args，那么调用程序时就可以包含多于定义时合法变量数量。这个情况下，所有的调用参数从args开始会合并到一个列表里（就是作为命令列表被使用）；这些合并的值被分配给本地变量args。

当包体被执行了，变量名一般会指向本地变量，当引用是会自动创建，当程序返回时会自动删除。每一个程序参数都会自动创建一个本地变量。全局变量可以使用语法::来分配。

当一个程序被调用，程序的返回值就是命令指定的返回值。如果程序没有执行一个明确的返回，那么它的返回值就是包体内最后一个命令的执行返回值。如果在执行包体时发生一个错误，那么整个程序包体返回相同的错误。

\textbf{return}命令的语法如下：
\begin{lyxcode}
return~?-code~code?~?value?
\end{lyxcode}
可选择的一对参数\textendash{}\textit{code code}允许将返回代码从TH\_OK修改为其它的状态代码。整个代码必须指定为一个数值。


\subsection{特殊命令}

TH包含了三个核心的命令用于辅助命令，它们是：
\begin{lyxcode}
breakpoint~args

rename~oldcmd~newcmd

uplevel~?level?~script
\end{lyxcode}
\textbf{breakpoint 命令}什么也不做。它作为占位符使用，用于替代调试时的断点。

\textbf{ rename 命令}重命名用户自定义或者内建的命令。老名称会被删除，新名称会被插入到解释器的命令表。

\textbf{uplevel 命令}在调用链更高一级的变量域执行一个命令。脚本参数在指定层级的变量域求值。\textbf{uplevel}命令返回求值的结果。如果level是一个正整数，那么它会给出之前执行命令的距离（提升到程序调用堆栈）。如果level省略了，那么它默认值是”1“。

举例说明，假设程序在顶层被调用，然后它调用了b，然后b调用了c。假设c调用uplevel命令。如果level是”1“或者略过了，那么命令将会在b的变量环境里执行。如果level设置为”2“那么命令会在a的变量环境里执行。如果level是”3“那么命令将会在顶层执行（即只有全局参数可访问）。

uplevel命令当执行命令时会引起程序从程序的调用链里消失。在上面的例子里，假设c调用了命令：
\begin{lyxcode}
uplevel~1~\{set~x~43;~d\}
\end{lyxcode}
当d是另外一个TH程序。set命令将修改b环境里的x变量，且d会在level 3执行。如果它回来执行命令
\begin{lyxcode}
uplevel~\{set~x~42\}
\end{lyxcode}
那么set命令将会在b的环境修改同样的参数x：在d执行时程序c不会出现在调用链里。


\section{使用字符串}

TH提供了\textbf{string 命令}来帮助使用字符串。string命令是一个有七个子命令的独立命令，使用第一个参数定义。第一个参数除了用于指定子命令外无任何目的。如果第一个参数没有匹配任何子命令，将会抛出错误信息。

七个子命令如下：
\begin{lyxcode}
string~length~string

string~compare~string1~string2

string~first~needle~haystack~?startindex?

string~last~needle~haystack~?startindex?

string~range~string~first~last

string~repeat~string~count

string~is~alnum~string
\end{lyxcode}
\textbf{string length 子命令}有一个参数，也就是字符。它返回字符串长度的十进制字符。当TH使用一个一位的字符编码那么字符的尺寸是字符的尺寸和比特的尺寸。

\textbf{string compare 子命令}执行一个逐个字符的对比，参数为string1和string2，与C语言strcmp程序一样。它返回一个十进制的值-1、0或1，取决于string1是否少于、等于或者大于string2。

\textbf{string first 子命令}为一个序列的字符串准确匹配参数指针的参数堆栈里进行搜索。如果发现，它返回堆栈里匹配索引的第一个字符的十进制值。如果没有发现，返回-1。，可选的正整数参数指定了开始搜索的位置；默认值为”0“，也就是堆栈里的第一个字符。

The \textbf{string last 子命令}为一个序列的字符串准确匹配参数指针的参数堆栈里进行搜索。如果发现，它返回堆栈里匹配索引的最后一个字符的十进制值。如果没有发现，返回-1。，可选的正整数参数指定了开始搜索的位置；默认值为”0“，也就是堆栈里的第一个字符。

\textbf{string range 子命令}返回给定参数的连续字符串的范围，从第一个字符索引直到最后一个字符索引。如果第一个小于零那么它会被当做零进行处理，如果最后一个大于或者等于字符的长度，那么它会被当做结束进行处理。如果第一个大于最后一个，那么将返回一个空白字符。

The \textbf{string repeat string count 子命令}返回一个根据count次数重复格式化后的字符串。参数count必须是正整数，如果count是零或者小于零那么将返回一个空字符。

\textbf{string is alnum 子命令}测试参数字符是否是字母或数字类的字符。比如：一个字符只是由字母或数字组成。如果字符是字母数组组成那么返回一个十进制字符值1否则为0。


\section{使用列表}

列表list是TH的基础数据结构。一个列表简单说就是序列化的对象、数字、单词、字符或其他列表。作为实例，下面的字符串是一个含四个对象的列表。第三个对象是一个含有二个对象的子列表：
\begin{lyxcode}
\{first~second~\{a~b\}~fourth\}~
\end{lyxcode}
TH有三个核心命令来使用列表：
\begin{lyxcode}
list~?arg1~?arg2?~...?

lindex~list~index

llength~list
\end{lyxcode}
命令\textbf{list command}返回一个有所有参数组成的列表。括号和反斜杠可根据需要添加，那么命令lindex可能在需要将原始参数进行提取时使用。比如，命令： 
\begin{lyxcode}
list~a~b~\{c~d~e\}~\{f~\{g~h\}\}
\end{lyxcode}
将返回：
\begin{lyxcode}
a~b~\{c~d~e\}~\{f~\{g~h\}\}
\end{lyxcode}
命令\textbf{lindex command}参数列表作为TH列表进行处理并返回元素索引的数值。参数index必须为整数值，0表示列表的第一个元素。提取元素值时，lindex命令命令遵守与TH解释器处理括号和引号、反斜杠相关的相同规则；然而，变量转义和命令转义不会触发它。如果索引值为负值或者大于等于元素的数量，将会返回一个空的字符。

命令\textbf{llength command}将参数列表作为列表处理，返回一个十进制的字符及元素的数量。
